﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.DomainObjects;
using DomainDriver.DomainModeling.Queries;

namespace DomainDriver.DomainModeling.StorageManagers
{
    public abstract class QueryableStorageManagerBase<KEY, KEYED_DOMAIN_OBJECT> : IStorageManager<KEY, KEYED_DOMAIN_OBJECT>
        where KEYED_DOMAIN_OBJECT : IKeyedDomainObject<KEY, KEYED_DOMAIN_OBJECT>
    {
        private IDataProvider m_DataProvider;

        public QueryableStorageManagerBase(IDataProvider dataProvider)
        {
            m_DataProvider = dataProvider;
        }

        public IDataProvider DataProvider
        {
            get { return m_DataProvider; }
        }

        public int Count()
        {
            IEnumerable<KEYED_DOMAIN_OBJECT> dataStore = GetQueryableDataStore();
            int count = dataStore.Count<KEYED_DOMAIN_OBJECT>();
            return count;
        }

        public bool Exists(KEY key)
        {
            List<KEY> keys = new List<KEY>();
            keys.Add(key);

            Func<KEYED_DOMAIN_OBJECT, bool> existsPredicate = GetKeyMatchingPredicate(keys);
            IEnumerable<KEYED_DOMAIN_OBJECT> dataStore = GetQueryableDataStore();
            bool exists = dataStore.Any<KEYED_DOMAIN_OBJECT>(existsPredicate);
            return exists;
        }

        public bool Exists(ICollection<KEY> keys)
        {
            foreach (KEY key in keys)
            {
                if (!Exists(key))
                { return false; }
            }
            return true;
        }

        public ICollection<KEY> FindKeys()
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => true);
            ICollection<KEY> keys = ReadRawKeys(queryPredicate);
            return keys;
        }

        public ICollection<KEY> FindKeys(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = query.GetQueryPredicate(DataProvider);
            ICollection<KEY> keys = ReadRawKeys(queryPredicate);
            return keys;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read()
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = ((KEYED_DOMAIN_OBJECT domainObject) => true);
            ICollection<KEYED_DOMAIN_OBJECT> rawObjects = ReadRawObjects(queryPredicate);
            List<KEYED_DOMAIN_OBJECT> copiedObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT rawObject in rawObjects)
            {
                copiedObjects.Add(rawObject.Copy());
            }
            return copiedObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(IQuery<KEYED_DOMAIN_OBJECT> query)
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = query.GetQueryPredicate(DataProvider);
            ICollection<KEYED_DOMAIN_OBJECT> rawObjects = ReadRawObjects(queryPredicate);
            List<KEYED_DOMAIN_OBJECT> copiedObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT rawObject in rawObjects)
            {
                copiedObjects.Add(rawObject.Copy());
            }
            return copiedObjects;
        }

        public ICollection<KEYED_DOMAIN_OBJECT> Read(ICollection<KEY> keys)
        {
            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = GetKeyMatchingPredicate(keys);
            ICollection<KEYED_DOMAIN_OBJECT> rawObjects = ReadRawObjects(queryPredicate);
            List<KEYED_DOMAIN_OBJECT> copiedObjects = new List<KEYED_DOMAIN_OBJECT>();

            foreach (KEYED_DOMAIN_OBJECT rawObject in rawObjects)
            {
                copiedObjects.Add(rawObject.Copy());
            }
            return copiedObjects;
        }

        public KEYED_DOMAIN_OBJECT Read(KEY key)
        {
            List<KEY> keys = new List<KEY>();
            keys.Add(key);

            Func<KEYED_DOMAIN_OBJECT, bool> queryPredicate = GetKeyMatchingPredicate(keys);
            ICollection<KEYED_DOMAIN_OBJECT> rawObjects = ReadRawObjects(queryPredicate);

            if (rawObjects.Count < 1)
            { return default(KEYED_DOMAIN_OBJECT); }
            KEYED_DOMAIN_OBJECT copiedObject = rawObjects.First<KEYED_DOMAIN_OBJECT>().Copy();
            return copiedObject;
        }

        public void Add(KEYED_DOMAIN_OBJECT domainObject)
        {
            WriteObject(domainObject, ChangeActionType.Create);
        }

        public void Remove(KEYED_DOMAIN_OBJECT domainObject)
        {
            WriteObject(domainObject, ChangeActionType.Delete);
        }

        public void Update(KEYED_DOMAIN_OBJECT domainObject)
        {
            WriteObject(domainObject, ChangeActionType.Update);
        }

        protected abstract IEnumerable<KEYED_DOMAIN_OBJECT> GetQueryableDataStore();
        protected abstract Func<KEYED_DOMAIN_OBJECT, bool> GetKeyMatchingPredicate(ICollection<KEY> keys);
        protected abstract ICollection<KEY> ReadRawKeys(Func<KEYED_DOMAIN_OBJECT, bool> predicate);
        protected abstract ICollection<KEYED_DOMAIN_OBJECT> ReadRawObjects(Func<KEYED_DOMAIN_OBJECT, bool> predicate);
        protected abstract void WriteObject(KEYED_DOMAIN_OBJECT domainObject, ChangeActionType changeAction);
    }
}